package com.blazebit.cdi.exclude;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMember;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessBean;
import javax.enterprise.util.Nonbinding;
import javax.inject.Named;
import org.apache.deltaspike.core.api.literal.AnyLiteral;
import org.apache.deltaspike.core.api.literal.DefaultLiteral;
import org.apache.deltaspike.core.util.bean.BeanBuilder;
import com.blazebit.apt.service.ServiceProvider;
import com.blazebit.cdi.exclude.annotation.ExcludeIfExists;
import com.blazebit.reflection.ReflectionUtils;
import java.lang.reflect.Field;
// Deactivate for now
//@ServiceProvider(Extension.class)
public class ExcludeIfExistsExtension implements Extension {
private final Map<AnnotatedType<Object>, ExcludeIfExists> possibleIncludes = new HashMap<AnnotatedType<Object>, ExcludeIfExists>();
private final Map<AnnotatedMember<Object>, ExcludeIfExists> possibleProducerIncludes = new HashMap<AnnotatedMember<Object>, ExcludeIfExists>();
private final Map<Bean<?>, Annotated> beans = new HashMap<Bean<?>, Annotated>();
private final Map<AnnotatedType<Object>, Bean<Object>> typeBeans = new HashMap<AnnotatedType<Object>, Bean<Object>>();
protected void vetoBeans(@Observes ProcessAnnotatedType<?> processAnnotatedType) {
ExcludeIfExists exclude = processAnnotatedType.getAnnotatedType().getAnnotation(ExcludeIfExists.class);
if (exclude == null) {
boolean veto = false;
for (AnnotatedField<?> field : processAnnotatedType.getAnnotatedType().getFields()) {
if (field.isAnnotationPresent(Produces.class)) {
exclude = field.getAnnotation(ExcludeIfExists.class);
if (exclude != null) {
veto = true;
possibleProducerIncludes.put((AnnotatedMember<Object>) field, exclude);
}
}
}
for (AnnotatedMethod<?> method : processAnnotatedType.getAnnotatedType().getMethods()) {
if (method.isAnnotationPresent(Produces.class)) {
exclude = method.getAnnotation(ExcludeIfExists.class);
if (exclude != null) {
veto = true;
possibleProducerIncludes.put((AnnotatedMember<Object>) method, exclude);
}
}
}
if (veto) {
processAnnotatedType.veto();
}
} else {
possibleIncludes.put((AnnotatedType<Object>) processAnnotatedType.getAnnotatedType(), exclude);
processAnnotatedType.veto();
}
}
protected void registerBean(@Observes ProcessBean<?> processBean) {
beans.put(processBean.getBean(), processBean.getAnnotated());
}
private boolean hasBean(Type[] types, Annotation[] qualifiers) {
BEAN_OUTER: for (Map.Entry<Bean<?>, Annotated> entry : beans.entrySet()) {
Bean<?> bean = entry.getKey();
boolean found = false;
for (Type t : types) {
if (bean.getTypes().contains(t)) {
found = true;
break;
}
}
if (!found) {
continue;
}
if (bean.getQualifiers().size() != qualifiers.length) {
continue;
}
OUTER: for (Annotation beanQualifier : bean.getQualifiers()) {
for (int i = 0; i < qualifiers.length; i++) {
if (areQualifiersEquivalent(beanQualifier, qualifiers[i])) {
continue OUTER;
}
}
// No qualifier matches the current bean qualifier
continue BEAN_OUTER;
}
return true;
}
return false;
}
private boolean areQualifiersEquivalent(Annotation beanQualifier, Annotation annotation) {
if (!beanQualifier.annotationType().equals(annotation.annotationType())) {
return false;
}
try {
for (Method m : beanQualifier.annotationType().getMethods()) {
if ("equals".equals(m.getName()) && m.getReturnType().equals(boolean.class) && m.getParameterTypes().length == 1 && m.getParameterTypes()[0].equals(Object.class)) {
continue;
}
if ("hashCode".equals(m.getName()) && m.getReturnType().equals(int.class) && m.getParameterTypes().length == 0) {
continue;
}
if ("toString".equals(m.getName()) && m.getReturnType().equals(String.class) && m.getParameterTypes().length == 0) {
continue;
}
if ("annotationType".equals(m.getName()) && m.getReturnType().equals(Class.class) && m.getParameterTypes().length == 0) {
continue;
}
if (!m.isAnnotationPresent(Nonbinding.class)) {
Object o1 = m.invoke(beanQualifier);
Object o2 = m.invoke(annotation);
if (!o1.equals(o2)) {
return false;
}
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return true;
}
protected void vetoBeans(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
boolean isOwbBug = isOwbBug(beanManager.getClass().getPackage());
for (Map.Entry<AnnotatedType<Object>, ExcludeIfExists> entry : possibleIncludes.entrySet()) {
AnnotatedType<Object> annotatedType = entry.getKey();
Class<?>[] types = entry.getValue().value();
Annotation[] qualifiers = getQualifiers(annotatedType, beanManager);
if (hasBean(types, qualifiers)) {
continue;
}
Bean<Object> bean = new BeanBuilder<Object>(beanManager).readFromType(annotatedType).create();
afterBeanDiscovery.addBean(bean);
}
for (Map.Entry<AnnotatedMember<Object>, ExcludeIfExists> entry : possibleProducerIncludes.entrySet()) {
AnnotatedMember<Object> annotatedMember = entry.getKey();
Class<?>[] types = entry.getValue().value();
Annotation[] qualifiers = getQualifiers(annotatedMember, beanManager);
if (hasBean(types, qualifiers)) {
continue;
}
Bean<Object> bean = typeBeans.get(annotatedMember.getDeclaringType());
if (bean == null) {
bean = new BeanBuilder<Object>(beanManager).readFromType(annotatedMember.getDeclaringType()).create();
typeBeans.put(annotatedMember.getDeclaringType(), bean);
afterBeanDiscovery.addBean(bean);
}
Bean<Object> producerBean = readFromMember(new BeanBuilder<Object>(beanManager), annotatedMember, bean, isOwbBug).create();
afterBeanDiscovery.addBean(producerBean);
}
}
private Annotation[] getQualifiers(Annotated annotatedElement, BeanManager beanManager) {
Set<Annotation> annotations = new HashSet<Annotation>();
for (Annotation annotation : annotatedElement.getAnnotations()) {
if (beanManager.isQualifier(annotation.annotationType())) {
annotations.add(annotation);
}
}
if (annotations.isEmpty()) {
annotations.add(new DefaultLiteral());
}
annotations.add(new AnyLiteral());
return annotations.toArray(new Annotation[annotations.size()]);
}
private BeanBuilder<Object> readFromMember(BeanBuilder<Object> beanBuilder, AnnotatedMember<Object> annotatedMember, Bean<Object> bean, boolean isOwbBug) {
// Init the qualifiers set
beanBuilder.qualifiers();
Set<Class<? extends Annotation>> stereotypes = new HashSet<Class<? extends Annotation>>();
for (Annotation annotation : annotatedMember.getAnnotations()) {
if (beanBuilder.getBeanManager().isQualifier(annotation.annotationType())) {
beanBuilder.addQualifier(annotation);
} else if (beanBuilder.getBeanManager().isScope(annotation.annotationType())) {
beanBuilder.scope(annotation.annotationType());
} else if (beanBuilder.getBeanManager().isStereotype(annotation.annotationType())) {
stereotypes.add(annotation.annotationType());
}
if (annotation instanceof Named) {
beanBuilder.name(((Named) annotation).value());
}
if (annotation instanceof Alternative) {
beanBuilder.alternative(true);
}
}
beanBuilder.stereotypes(stereotypes);
if (beanBuilder.getScope() == null) {
beanBuilder.scope(Dependent.class);
}
boolean isDependent = beanBuilder.getScope() == Dependent.class;
Class<?> declaringClass = annotatedMember.getDeclaringType().getJavaClass();
if (annotatedMember instanceof AnnotatedField<?>) {
AnnotatedField<Object> annotatedField = (AnnotatedField<Object>) annotatedMember;
Field field = annotatedField.getJavaMember();
Class<?> rawType = ReflectionUtils.getResolvedFieldType(declaringClass, field);
beanBuilder.beanClass(rawType);
beanBuilder.beanLifecycle(new ProducerFieldCreationalContext(bean, annotatedField, beanBuilder.getBeanManager()));
} else {
AnnotatedMethod<Object> annotatedMethod = (AnnotatedMethod<Object>) annotatedMember;
Method method = annotatedMethod.getJavaMember();
Class<?> rawType = ReflectionUtils.getResolvedMethodReturnType(declaringClass, method);
beanBuilder.beanClass(rawType);
ProducerMethodCreationalContext ctx = new ProducerMethodCreationalContext(bean, annotatedMethod, beanBuilder.getBeanManager(), isDependent && isOwbBug);
beanBuilder.beanLifecycle(ctx);
beanBuilder.injectionPoints(ctx.getInjectionPoints());
}
beanBuilder.types(annotatedMember.getTypeClosure());
if (beanBuilder.getQualifiers().isEmpty()) {
beanBuilder.addQualifier(new DefaultLiteral());
}
beanBuilder.addQualifier(new AnyLiteral());
return beanBuilder;
}
private boolean isOwbBug(Package owbPackage) {
if (!owbPackage.getName().startsWith("org.apache.webbeans")) {
return false;
}
String[] versionParts = owbPackage.getImplementationVersion().split("\\.");
Integer minor = Integer.parseInt(versionParts[1]);
Integer bugfix = Integer.parseInt(versionParts[2]);
// TODO: update this as soon as there is a known version which fixes this
return minor <= 2 && bugfix <= 6;
}
}